# frozen_string_literal: true

require 'spec_helper'

RSpec.describe VulnerabilityExports::ExportService, feature_category: :vulnerability_management do
  describe '::export' do
    let(:vulnerability_export) { create(:vulnerability_export) }
    let(:mock_service_object) { instance_double(described_class, export: true) }

    subject(:export) { described_class.export(vulnerability_export) }

    before do
      allow(described_class).to receive(:new).and_return(mock_service_object)
    end

    it 'instantiates a new instance of the service class and sends export message to it' do
      export

      expect(described_class).to have_received(:new).with(vulnerability_export)
      expect(mock_service_object).to have_received(:export)
    end
  end

  describe '#export' do
    let(:vulnerability_export) { create(:vulnerability_export, :created) }
    let(:service_object) { described_class.new(vulnerability_export) }

    subject(:export) { service_object.export }

    context 'generating the export file' do
      let(:lease_name) { "vulnerability_exports_export:#{vulnerability_export.id}" }

      before do
        allow(service_object).to receive(:in_lock)
      end

      it 'runs synchronized with distributed semaphore' do
        export

        expect(service_object).to have_received(:in_lock).with(lease_name, ttl: 1.hour)
      end
    end

    context 'when the vulnerability_export is not in `created` state' do
      before do
        allow(vulnerability_export).to receive(:created?).and_return(false)
        allow(service_object).to receive(:generate_export)
      end

      it 'does not execute export file generation logic' do
        export

        expect(service_object).not_to have_received(:generate_export)
      end
    end

    context 'when the vulnerability_export is in `created` state' do
      before do
        allow(VulnerabilityExports::ExportDeletionWorker).to receive(:perform_in)
      end

      context 'when the export generation fails' do
        let(:error) { RuntimeError.new('foo') }

        before do
          allow(service_object).to receive(:generate_export_file).and_raise(error)
        end

        it 'sets the state of export back to `created`' do
          expect { export }.to raise_error(error)
          expect(vulnerability_export.reload.created?).to be_truthy
        end

        it 'schedules the export deletion background job' do
          expect { export }.to raise_error(error)
          expect(VulnerabilityExports::ExportDeletionWorker).to have_received(:perform_in).with(1.hour, vulnerability_export.id)
        end
      end

      context 'when the export generation succeeds' do
        before do
          allow(service_object).to receive(:generate_export_file)
          allow(vulnerability_export).to receive(:start!)
          allow(vulnerability_export).to receive(:finish!)
        end

        it 'marks the state of export object as `started` and then `finished`' do
          export

          expect(vulnerability_export).to have_received(:start!).ordered
          expect(vulnerability_export).to have_received(:finish!).ordered
        end

        it 'schedules the export deletion background job' do
          export

          expect(VulnerabilityExports::ExportDeletionWorker).to have_received(:perform_in).with(1.hour, vulnerability_export.id)
        end
      end

      context 'when the export format is csv' do
        let(:vulnerabilities) { Vulnerability.none }
        let(:mock_relation) { double(:relation, with_findings_scanner_identifiers_and_notes: vulnerabilities) }
        let(:mock_vulnerability_finder_service_object) { instance_double(Security::VulnerabilitiesFinder, execute: mock_relation) }
        let(:exportable_full_path) { 'foo' }
        let(:time_suffix) { Time.current.utc.strftime('%FT%H%M') }
        let(:expected_file_name) { "#{exportable_full_path}_vulnerabilities_#{time_suffix}.csv" }

        before do
          allow(Security::VulnerabilitiesFinder).to receive(:new).and_return(mock_vulnerability_finder_service_object)
          allow(vulnerability_export.exportable).to receive(:full_path).and_return(exportable_full_path)
        end

        around do |example|
          freeze_time { example.run }
        end

        it 'calls the VulnerabilityExports::Exporters::CsvService which sets the file and filename' do
          expect { export }.to change { vulnerability_export.file }
                           .and change { vulnerability_export.file&.filename }.from(nil).to(expected_file_name)
        end
      end
    end
  end
end
